package com.rizzo.analytics.services.repo;


import com.rizzo.analytics.domain.PersistentEntity;
import org.joda.time.DateTime;
import org.joda.time.Period;
import org.springframework.data.jpa.domain.Specification;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.ListJoin;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.persistence.metamodel.ListAttribute;
import javax.persistence.metamodel.SingularAttribute;
import java.util.Date;

public abstract class EntitySpecs<P extends PersistentEntity, C extends PersistentEntity> {

	public abstract Specification<P> hasMoreChildrenThan(long childCount);

	public abstract Specification hasChildrenWithProperty(final ListAttribute<P, C> joinProperty,
			final SingularAttribute<C, Object> childProperty, final Object childPropertyValue);

	public abstract Specification<P> fallsInPeriod(DateTime startDate, Period period);

	public abstract Specification<P> fallsInFuture();

	public abstract Specification<P> fallsInPast();

	protected Predicate createHasMoreChildrenThanPredicate(Root<P> root, CriteriaQuery<?> criteriaQuery,
			CriteriaBuilder criteriaBuilder, SingularAttribute<P, Long> parentIdAttribute,
			ListAttribute<P, C> parentChildAttribute, long childCount) {
		criteriaQuery.groupBy(root.get(parentIdAttribute));
		final ListJoin<P, C> children = root.join(parentChildAttribute);
		final Expression<Boolean> countGreaterThanExpression = criteriaBuilder.greaterThan(criteriaBuilder.count(children), childCount);
		criteriaQuery.having(countGreaterThanExpression);
		return criteriaQuery.getRestriction();
	}

	protected Predicate createHasChildrenWithPropertyPredicate(Root<P> root, CriteriaQuery<?> criteriaQuery,
			CriteriaBuilder criteriaBuilder, ListAttribute<P, C> parentChildAttribute,
			SingularAttribute<C, Object> childProperty, Object childPropertyValue) {
		criteriaQuery.distinct(true);
		final ListJoin<P, C> children = root.join(parentChildAttribute);
		final Path<Object> child = children.get(childProperty);
		return criteriaBuilder.equal(child, childPropertyValue);
	}

	protected Predicate fallsInPeriodPredicate(Root<P> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder,
			SingularAttribute<P, Date> dateProperty, DateTime startDate, Period period) {
		DateTime endDate = startDate.plus(period);
		final Predicate afterStartDate = criteriaBuilder.greaterThan(root.get(dateProperty), startDate.toDate());
		final Predicate beforeEndDate = criteriaBuilder.lessThan(root.get(dateProperty), endDate.toDate());
		return criteriaBuilder.and(afterStartDate, beforeEndDate);
	}

	protected Predicate fallsInFuturePredicate(Root<P> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder,
			SingularAttribute<P, Date> dateProperty) {
		DateTime now = new DateTime();
		return criteriaBuilder.greaterThan(root.get(dateProperty), now.toDate());
	}

	protected Predicate fallsInPastPredicate(Root<P> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder,
			SingularAttribute<P, Date> dateProperty) {
		DateTime now = new DateTime();
		return criteriaBuilder.lessThan(root.get(dateProperty), now.toDate());
	}


}
